package edu.usc.cs523.client;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.plugins.ZipLocator;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapText;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.network.Client;
import com.jme3.network.ClientStateListener;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.serializing.Serializer;
import com.jme3.renderer.RenderManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import edu.usc.cs523.effect.ExplosionEffect;

import edu.usc.cs523.settings.Global;
import edu.usc.cs523.util.CeceMessage;

import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 * test
 * @author normenhansen
 */
public class ClientMain extends SimpleApplication implements MessageListener<Client>, ClientStateListener, ActionListener, ScreenController {

    private int id;
    private Client client;
    private Spatial sceneModel;
    private BulletAppState bulletAppState;
    private RigidBodyControl landscape;
    private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private Rival rival;
    private Rival myself;
    private boolean left = false, right = false, up = false, down = false;
    //effetcs
    private ExplosionEffect explosion;
    private Vector3f rival_last_position;
    private Vector3f player_last_lookat_direction;
    private boolean alive;
    private PanelManager panelManager;
    private ItemManager itemManager;

    //player status
    @Override
    public void simpleInitApp() {
        try {
            initialVariables();
        } catch (Exception ex) {
            Logger.getLogger(ClientMain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void connectOne() {
        try {
            initialNetwork(1);
            initialKeys();
            initialScene();
            initialCross();

            panelManager.gotoGame();
            panelManager.addItem("Speed");
            panelManager.addItem("Power");
            panelManager.addItem("Heal");

            panelManager.setHP("HP:100");
            panelManager.setMP("MP:100");
        } catch (IOException ex) {
            Logger.getLogger(ClientMain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void connectTwo() {
        try {
            initialNetwork(2);

            initialKeys();
            initialScene();
            initialCross();

            panelManager.gotoGame();
            panelManager.addItem("Speed");
            panelManager.addItem("Power");
            panelManager.addItem("Heal");

            panelManager.setHP("HP:100");
            panelManager.setMP("MP:100");
        } catch (IOException ex) {
            Logger.getLogger(ClientMain.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void simpleUpdate(float tpf) {
        Vector3f camDir = cam.getDirection().clone().multLocal(0.6f);
        Vector3f camLeft = cam.getLeft().clone().multLocal(0.4f);

        if (player_last_lookat_direction == null) {
            player_last_lookat_direction = cam.getDirection();
        }

        walkDirection.set(0, 0, 0);
        if (left) {
            walkDirection.addLocal(camLeft);
            CeceMessage message = MessageFactory.getInstance().getMoveLeftMessage(id, false, player.getPhysicsLocation(), cam.getDirection());
            client.send(message);
        }
        if (right) {
            walkDirection.addLocal(camLeft.negate());
            CeceMessage message = MessageFactory.getInstance().getMoveRightMessage(id, false, player.getPhysicsLocation(), cam.getDirection());
            client.send(message);
        }
        if (up) {
            walkDirection.addLocal(camDir);
            CeceMessage message = MessageFactory.getInstance().getMoveForwardMessage(id, false, player.getPhysicsLocation(), cam.getDirection());
            client.send(message);
        }
        if (down) {
            walkDirection.addLocal(camDir.negate());
            CeceMessage message = MessageFactory.getInstance().getMoveBackMessage(id, false, player.getPhysicsLocation(), cam.getDirection());
            client.send(message);
        }

        if (explosion.isPlaying()) {
            explosion.run(tpf);
        }

        if (player != null) {
            player.setWalkDirection(walkDirection.mult(myself.Speed_Factor));
            cam.setLocation(player.getPhysicsLocation());

            Vector3f diff = cam.getDirection().normalize().subtract(player_last_lookat_direction);

            if (walkDirection.equals(Vector3f.ZERO) && !(diff.x < 0.000001 && diff.y < 0.000001 && diff.z < 0.000001)) {
                CeceMessage message = MessageFactory.getInstance().getMoveDirectionMessage(id, false, player.getPhysicsLocation(), cam.getDirection());
                client.send(message);
            }

            player_last_lookat_direction = cam.getDirection();


            new Thread() {

                @Override
                public void run() {
                    enqueue(new Callable<Void>() {

                        public Void call() throws Exception {
                            if (rival != null) {
                                if (rival_last_position == null) {
                                    rival_last_position = rival.getPosition();
                                }
                                if (rival.getModel() != null) {
                                    Vector3f location = rival.getPosition().subtract(new Vector3f(0.0f, 3.0f, 0.0f));
                                    rival.getSpatial().setLocalTranslation(location);
                                    rival.getSpatial().lookAt(rival.getPosition().add(new Vector3f(rival.getDirection().x, -3.0f, rival.getDirection().z)), cam.getUp());
                                    rival_last_position = rival.getPosition();
                                }
                            }
                            return null;
                        }
                    });
                }
            }.start();

        }
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }

    @Override
    public void destroy() {
        if (client != null) {
            client.close();
        }
        super.destroy();
    }

    public void messageReceived(Client source, Message m) {
        if (m instanceof CeceMessage) {
            CeceMessage message = (CeceMessage) m;
            switch (message.getAction()) {
                case Global.ACTION_CONNECT:
                    connectResponse(message, source);
                    break;
                case Global.ACTION_ACTION:
                    actionResponse(message, source);
                    break;
                case Global.ACTION_DISCONNECT:
                    disconnectResponse(message, source);
                    break;
                case Global.ACTION_STATUS:
                    statusResponse(message, source);
                    break;
                case Global.ACTION_CHANGE:
                    changeResponse(message, source);
                    break;
            }
        }
    }

    public void clientConnected(Client c) {
    }

    public void clientDisconnected(Client c, DisconnectInfo info) {
    }

    public static void main(String[] args) {
        ClientMain app = new ClientMain();
        app.setPauseOnLostFocus(false);
        app.start();
    }

    private void initialNetwork(int model_number) throws IOException {
        client = Network.connectToServer(Global.server_host, Global.server_port_number);
        //register the mssage class
        Serializer.registerClass(CeceMessage.class);
        //add the listener
        client.addMessageListener(this, CeceMessage.class);
        //add the cient state listener
        client.addClientStateListener(this);
        //start the client
        client.start();

        CeceMessage connMessage = MessageFactory.getInstance().getConnectMessage(-1, model_number);
        connMessage.setReliable(true);
        client.send(connMessage);
    }

    private void initialKeys() {
        // You can map one or several inputs to one named action
        //becky, rememeber to replace the value in the keytrigger constructor with
        //the values you defined in gloabl.java
        inputManager.addMapping("Left", new KeyTrigger(Global.LEFT_KEY));
        inputManager.addMapping("Right", new KeyTrigger(Global.RIGHT_KEY));
        inputManager.addMapping("Forward", new KeyTrigger(Global.FORWARD_KEY));
        inputManager.addMapping("Back", new KeyTrigger(Global.BACK_KEY));
        inputManager.addMapping("Attack", new KeyTrigger(Global.ATTACK_KEY), new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping("Jump", new KeyTrigger(Global.JUMP_KEY));
        inputManager.addMapping("Inventory", new KeyTrigger(Global.INVENTORY_KEY));
        inputManager.addMapping("UseInventory", new KeyTrigger(Global.USE_INVENTORY_KEY));
        // Add the names to the action listener.
        inputManager.addListener(this, new String[]{"Left", "Right", "Forward", "Back", "Attack", "Jump", "Inventory", "UseInventory"});
    }

    private void initialScene() {
        inputManager.setCursorVisible(false);

        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
//        bulletAppState.getPhysicsSpace().enableDebug(assetManager);

        viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
        flyCam.setMoveSpeed(100);
        setUpLight();

        assetManager.registerLocator("assets/Models/maps/town/town.zip", ZipLocator.class.getName());
        sceneModel = assetManager.loadModel("main.scene");
        sceneModel.setLocalScale(2f);

        CollisionShape sceneShape =
                CollisionShapeFactory.createMeshShape((Node) sceneModel);
        landscape = new RigidBodyControl(sceneShape, 0);
        sceneModel.addControl(landscape);

        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1);
        player = new CharacterControl(capsuleShape, 0.05f);
        player.setJumpSpeed(20);
        player.setFallSpeed(30);
        player.setGravity(50);
        player.setPhysicsLocation(Global.START_POSITION);

        rootNode.attachChild(sceneModel);
        bulletAppState.getPhysicsSpace().add(landscape);
        bulletAppState.getPhysicsSpace().add(player);
    }

    private void setUpLight() {
        // We add light so we see the scene
        AmbientLight al = new AmbientLight();
        al.setColor(ColorRGBA.White.mult(1.3f));
        rootNode.addLight(al);

        DirectionalLight dl = new DirectionalLight();
        dl.setColor(ColorRGBA.White);
        dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
        rootNode.addLight(dl);
    }

    private void initialVariables() {
        id = -1;
        explosion = new ExplosionEffect(assetManager, renderManager, speed);
        explosion.setPlaying(false);
        alive = true;
        myself = new Rival();
        panelManager = new PanelManager(guiNode, assetManager,
                inputManager,
                audioRenderer,
                guiViewPort, this);
        itemManager = new ItemManager(myself,panelManager);
        inputManager.setCursorVisible(true);
    }

    public void onAction(String name, boolean value, float tpf) {
        clientMove(name, value, tpf);

        if (name.toLowerCase().equals("attack")) {
            attack();
        }
        if (name.toLowerCase().equals("inventory")) {
            moveInventory();
        }
        if (name.toLowerCase().equals("useinventory")) {
            useInventory();
        }
    }

    private void clientMove(String binding, boolean value, float tpf) {
        if (binding.equals("Left")) {
            if (value) {
                left = true;
            } else {
                left = false;
            }
        } else if (binding.equals("Right")) {
            if (value) {
                right = true;
            } else {
                right = false;
            }
        } else if (binding.equals("Forward")) {
            if (value) {
                up = true;
            } else {
                up = false;
            }
        } else if (binding.equals("Back")) {
            if (value) {
                down = true;
            } else {
                down = false;
            }
        } else if (binding.equals("Jump")) {
            player.jump();
        }
    }

    private void attack() {
        CollisionResults results = new CollisionResults();
        Ray ray = new Ray(cam.getLocation(), cam.getDirection());
        rootNode.collideWith(ray, results);

        for (int i = 0; i < results.size(); i++) {
            System.out.println(results.getCollision(i).getGeometry().getName().toLowerCase());
            if (results.getCollision(i).getGeometry().getName().toLowerCase().contains("chassis")
                    || results.getCollision(i).getGeometry().getName().toLowerCase().contains("tank")) {
                CeceMessage message = MessageFactory.getInstance().getShotMessage(id, rival.getId(), (int) myself.Power_Factor * Global.SHOOT_DAMAGE);
                client.send(message);

                rival.HP -= message.getMessage();
                panelManager.setMessage("Hit Target!");
                if (rival.HP <= 0) {
                    if (!explosion.isPlaying()) {
                        rootNode.attachChild(explosion.getExplosionEffect());
                        explosion.getExplosionEffect().setLocalTranslation(rival.getPosition());
                        explosion.setPlaying(true);

                        rootNode.detachChild(rival.getModel());
                    }
                }
            } else {
                if (!explosion.isPlaying()) {
                    rootNode.attachChild(explosion.getExplosionEffect());
                    explosion.getExplosionEffect().setLocalTranslation(results.getCollision(i).getContactPoint());
                    explosion.setPlaying(true);
                }
            }
        }
    }

    private void moveInventory() {
        panelManager.moveCursor();
    }

    private void useInventory() {
        String item = panelManager.useItem();
        System.out.println(item);
        if (item.toLowerCase().equals("speed")) {
            panelManager.setMessage("******SPEED UP******");
            itemManager.speedup();
        }
        if (item.toLowerCase().equals("power")) {
            panelManager.setMessage("******POWER UP******");
            itemManager.powerup();
        }
        if (item.toLowerCase().equals("heal")) {
            panelManager.setMessage("******HEAL******");
            itemManager.healup();
        }
        if (item.toLowerCase().equals("magic")) {
            itemManager.magicup();
        }
        if (item.toLowerCase().equals("invisible")) {
            itemManager.invisible();
        }
    }

    private void connectResponse(CeceMessage message, Client source) {
        if (this.id == -1) {
            this.id = message.getId();
        } else {
            if (rival == null) {
                System.out.println("connect:add rival id:" + message.getId());
                panelManager.setMessage("User " + message.getId() + " Joined Game");
                rival = new Rival(assetManager);
                rival.setId(message.getId());
                rival.setDirection(Global.START_DIRECTION);
                rival.setPosition(Global.START_POSITION);
                rival.setModel_num(message.getMessage());

                rootNode.attachChild(rival.getModel());
            }
        }
    }

    private void actionResponse(CeceMessage message, Client source) {
        if (message.getId() != this.id) {
            if (rival != null && rival.getId() == message.getId()) {
                walkDirection.set(0, 0, 0);
                switch (message.getMessage()) {
                    case Global.MOVE_FORWARD:
                        walkDirection.addLocal(rival.direction);
                        rival.setDirection(message.getDirection());
                        rival.setPosition(message.getPosition());
                        break;
                    case Global.MOVE_BACK:
                        walkDirection.addLocal(rival.direction.negate());
                        rival.setDirection(message.getDirection());
                        rival.setPosition(message.getPosition());
                        break;
                    case Global.MOVE_LEFT:
                        walkDirection.addLocal(rival.direction.normalize().cross(new Vector3f(0, 1, 0)));
                        rival.setDirection(message.getDirection());
                        rival.setPosition(message.getPosition());
                        break;
                    case Global.MOVE_RIGHT:
                        walkDirection.addLocal(rival.direction.normalize().cross(new Vector3f(0, 1, 0)).negate());
                        rival.setDirection(message.getDirection());
                        rival.setPosition(message.getPosition());
                        break;
                    case Global.MOVE_DIRECTION:
                        rival.setDirection(message.getDirection());
                        break;
                }
            }
        } else {
            //check network latency
        }
    }

    private void disconnectResponse(CeceMessage message, Client source) {
        if (message.getId() == this.id) {
        } else if (this.rival != null && message.getId() == rival.getId()) {
            panelManager.setMessage("User " + message.getId() + " Left Game");
            rootNode.detachChild(this.rival.getModel());
            this.rival = null;
        }
    }

    private void statusResponse(CeceMessage message, Client source) {
        if (message.getId() != this.id) {
            System.out.println("status:add rival id:" + message.getId());
            panelManager.setMessage("Welcome to Super Cece");
            rival = new Rival(assetManager);
            rival.setId(message.getId());
            rival.setDirection(new Vector3f(message.getDirection()));
            rival.setPosition(message.getPosition());
            rival.setModel_num(message.getMessage());

            rootNode.attachChild(rival.getModel());
        } else {
            //latency check?
        }
    }

    private void initialCross() {
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+"); // crosshairs
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    private void changeResponse(CeceMessage message, Client source) {
        if (message.getId() == id) {
            //latency check?
        } else {
            if (alive) {
                if (message.getTarget() == id) {
                    myself.HP -= message.getMessage();
                    panelManager.setMessage("You Are Hit!!!!");
                    if (myself.HP <= 0) {
                        alive = false;

                        AmbientLight a2 = new AmbientLight();
                        a2.setColor(ColorRGBA.Red.mult(2.3f));
                        rootNode.addLight(a2);

                        DirectionalLight d2 = new DirectionalLight();
                        d2.setColor(ColorRGBA.Red);
                        d2.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal());
                        rootNode.addLight(d2);
                        panelManager.setMessage("*******You Are Dead*******");
                    }
                    panelManager.setHP("HP:" + Math.max(0, myself.HP));
                }
            }
        }
    }

    public void bind(Nifty nifty, Screen screen) {
    }

    public void onStartScreen() {
    }

    public void onEndScreen() {
    }
}
